package com.edu.compile.util;

import com.alibaba.fastjson.JSON;
import com.edu.model.Response;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileUrlResource;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class LocalNodeEnv {

    private LocalNodeEnv() {
    }

    private static final Logger LOGGER = LoggerFactory.getLogger(LocalNodeEnv.class);
    private static final String CLASSES_PATH;
    private static final boolean isLinux;
    public static final String WIN_NODE_ENV_ZIP = "node-win-x64.zip";
    private static final String LINUX_NODE_ENV_ZIP = "node-linux-x64.zip";
    private static final String NODE_WIN_X_64 = "node-win-x64";
    private static final String NODE_LINUX_X_64 = "node-linux-x64";
    private static final String ESLINT_PATH;
    private static final String USE_NODE;
    private static final String ESLINT_RULE;
    private static final String FULL_COMMAND;

    private static volatile boolean hasNode = false;

    static {
        isLinux = isLinux();
        CLASSES_PATH = getClassPath();
        decompressNodeEnv();
        USE_NODE = CLASSES_PATH + File.separator + (isLinux ? NODE_LINUX_X_64 : NODE_WIN_X_64) + "/bin/node" + (isLinux ? "" : ".exe");
        ESLINT_PATH = CLASSES_PATH + File.separator + (isLinux ? NODE_LINUX_X_64 : NODE_WIN_X_64) + "/node_global/bin/eslint" + (isLinux ? "" : ".cmd");
        ESLINT_RULE = CLASSES_PATH + File.separator + "check/rule.json";
        FULL_COMMAND = (isLinux ? USE_NODE + " " : "") + ESLINT_PATH + " -c " + ESLINT_RULE + " ";
        checkNodeEnv();
        test();
        LOGGER.info("LocalNodeEnv isLinux: {}, classpath :{}", isLinux(), CLASSES_PATH);
    }

    public static void init() {
        LOGGER.info("LocalNodeEnv init success...");
    }

    public static void main(String[] args) throws IOException {
        System.out.println("+++++++++++++++++");
    }

    public static void test() {
        try {
            ClassPathResource resource = new ClassPathResource("check/test.js");
            Response response = hasError(resource.getFile());
            LOGGER.error("response: {}", JSON.toJSONString(response));
        } catch (Exception e) {
            LOGGER.error("test js error: {}", e.getMessage());
        }
    }

    private static boolean isLinux() {
        String osName = System.getProperty("os.name");
        LOGGER.info("start init {} node env", osName);
        return osName.toLowerCase().indexOf("linux") >= 0;
    }

    private static void decompressNodeEnv() {
        String zipFilePath = CLASSES_PATH + File.separator + (isLinux ? LINUX_NODE_ENV_ZIP : WIN_NODE_ENV_ZIP);
        boolean flag = unzip(zipFilePath, CLASSES_PATH + File.separator, "gbk");
        LOGGER.info("uncompressed path: {}, status: {}", zipFilePath, flag);
    }

    /**
     * 假如是多module时，此模块被依赖，那么需要获取到依赖的jar包中的classpath，通过此种方法
     *
     * @return
     */
    private static String getClassPathIfDependency() {
        URL zipURL = LocalNodeEnv.class.getClassLoader().getResource((isLinux ? LINUX_NODE_ENV_ZIP : WIN_NODE_ENV_ZIP));
        try {
            FileUrlResource resource = new FileUrlResource(zipURL.getPath());
            File classPathFile = resource.getFile();
            return classPathFile.getParent();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    private static String getClassPath() {
        ClassPathResource resource = new ClassPathResource("");
        try {
            return resource.getFile().getPath();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

    public static Response hasError(File file) {
        if (file == null || !file.exists()) {
            LOGGER.error("check file is null or is not exists");
            return Response.success();
        }
        try {
            return hasError(file.getCanonicalPath());
        } catch (Exception e) {
            LOGGER.error("file CanonicalPath error: {}", e.getMessage());
        }
        return Response.success();
    }

    public static Response hasError(String filepath) {
        if (hasNode()) {
            Runtime runtime = Runtime.getRuntime();
            InputStream inputStream = null;
            InputStream errorStream = null;
            Process exec = null;
            try {
                String cmd = FULL_COMMAND + filepath;
                LOGGER.info("command: {}", cmd);
                exec = runtime.exec(cmd);
                exec.waitFor();
                errorStream = exec.getErrorStream();
                if (errorStream.available() > 0) {
                    byte[] er = new byte[errorStream.available()];
                    errorStream.read(er);
                    LOGGER.error("command exec error: {}", new String(er, "gbk"));
                    return Response.success();
                } else {
                    inputStream = exec.getInputStream();
                    byte[] in = new byte[inputStream.available()];
                    inputStream.read(in);
                    String message = new String(in);
                    if (StringUtils.isBlank(message)) {
                        return Response.success();
                    } else {
                        return Response.fail(message);
                    }
                }
            } catch (Exception e) {
                LOGGER.error("check file error: {}", e.getMessage());
            } finally {
                close(exec, inputStream, errorStream);
            }
        }
        return Response.success();
    }

    public static boolean hasNode() {
        return hasNode;
    }

    private static void checkNodeEnv() {
        Runtime runtime = Runtime.getRuntime();
        InputStream inputStream = null;
        Process exec = null;
        try {
            exec = runtime.exec(USE_NODE + " -v");
            exec.waitFor();
            inputStream = exec.getInputStream();
            byte[] in = new byte[inputStream.available()];
            inputStream.read(in);
            LOGGER.info("node check success: {}", new String(in));
            hasNode = true;
        } catch (Exception e) {
            LOGGER.error("node check error: {}", e.getMessage());
            hasNode = false;
        } finally {
            close(exec, inputStream);
        }
    }

    /**
     * 解压zip文件
     *
     * @param zipFilePath 目标文件
     * @param descDir     解压后存放的位置
     * @return true/false
     */
    private static boolean unzip(String zipFilePath, String descDir, String charSet) {
        boolean flag = false;

        File zipFile = new File(zipFilePath);
        if (!zipFile.exists()) {
            return flag;
        }
        try (ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(zipFile), Charset.forName(charSet))) {
            ZipEntry entry = null;
            while ((entry = zipInputStream.getNextEntry()) != null) {
                try {
                    String filename = descDir + entry.getName();
                    File descFile = new File(filename);
                    if (entry.isDirectory()) {
                        if (!descFile.exists()) {
                            descFile.mkdirs();
                        }
                    } else {
                        if (descFile.exists()) {
                            LOGGER.info(descFile.getName() + " exists.");
                            continue;
                        }
                        LOGGER.info(descFile.getName() + " is being unzipped.");
                        try (FileOutputStream fos = new FileOutputStream(descFile); BufferedOutputStream bos = new BufferedOutputStream(fos)) {
                            byte[] bytes = new byte[1024 * 2];
                            int len;
                            while ((len = zipInputStream.read(bytes)) != -1) {
                                bos.write(bytes, 0, len);
                            }
                            bos.flush();
                            if (descFile.getName().equals("node")) {
                                LOGGER.info("node can execute: {}", descFile.canExecute());
                                boolean b = descFile.setExecutable(true);
                                LOGGER.info("node set executable: {}", b);
                            }
                        } catch (Exception e) {
                            throw e;
                        }
                    }
                } finally {
                    zipInputStream.closeEntry();
                }
            }
            flag = true;
        } catch (Exception e) {
            LOGGER.error("unzip error: {}", e.getMessage());
        }
        return flag;
    }

    private static void close(Process process, InputStream... streams) {
        try {
            if (streams != null && streams.length > 0) {
                for (int i = 0; i < streams.length; i++) {
                    InputStream stream = streams[i];
                    if (stream != null) {
                        try {
                            stream.close();
                        } catch (IOException e) {
                            LOGGER.error("stream close error: {}", e.getMessage());
                        }
                    }
                }
            }
            if (process != null) {
                process.destroy();
            }
        } catch (Exception e) {
            LOGGER.error("process destroy error: {}", e.getMessage());
        }
    }
}